/** * GET /_emdash/api/admin/roles/:id — one role (id or slug) * PUT /_emdash/api/admin/roles/:id — update name, description, level, policies * DELETE /_emdash/api/admin/roles/:id — delete (not built-in, not in use) */ import type { APIRoute } from "astro"; import { requirePerm } from "#api/authorize.js"; import { apiError, apiSuccess, handleError } from "#api/error.js"; import { isParseError, parseBody } from "#api/parse.js"; import { roleUpdateBody } from "#api/schemas.js"; import { invalidateAuthzCache } from "#auth/authz.js"; import { AuthzRepository } from "#db/repositories/authz.js"; import { authzErrorResponse } from "./index.js"; export const prerender = false; export const GET: APIRoute = async ({ params, locals }) => { const { emdash, user } = locals; if (!emdash?.db) return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500); const denied = requirePerm(user, "roles:read"); if (denied) return denied; if (!params.id) return apiError("MISSING_PARAM", "Role id required", 400); try { const item = await new AuthzRepository(emdash.db).getRole(params.id); if (!item) return apiError("NOT_FOUND", "Role not found", 404); return apiSuccess({ item }); } catch (error) { return handleError(error, "Failed to get role", "ROLE_GET_ERROR"); } }; export const PUT: APIRoute = async ({ params, request, locals }) => { const { emdash, user } = locals; if (!emdash?.db) return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500); const denied = requirePerm(user, "roles:manage"); if (denied) return denied; if (!params.id) return apiError("MISSING_PARAM", "Role id required", 400); try { const body = await parseBody(request, roleUpdateBody); if (isParseError(body)) return body; const repo = new AuthzRepository(emdash.db); // Editing your own role is refused for the same reason self-demotion // is: an admin who removes roles:manage from their own role has no way // back. Another administrator must make that change. const target = await repo.getRole(params.id); if (!target) return apiError("NOT_FOUND", "Role not found", 404); if (user!.roleId === target.id && (body.policies !== undefined || body.level !== undefined)) { return apiError( "SELF_ROLE_CHANGE", "You cannot change the policies or level of your own role", 400, ); } const item = await repo.updateRole(target.id, body); invalidateAuthzCache(); return apiSuccess({ item }); } catch (error) { return ( authzErrorResponse(error) ?? handleError(error, "Failed to update role", "ROLE_UPDATE_ERROR") ); } }; export const DELETE: APIRoute = async ({ params, locals }) => { const { emdash, user } = locals; if (!emdash?.db) return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500); const denied = requirePerm(user, "roles:manage"); if (denied) return denied; if (!params.id) return apiError("MISSING_PARAM", "Role id required", 400); try { await new AuthzRepository(emdash.db).deleteRole(params.id); invalidateAuthzCache(); return apiSuccess({ success: true }); } catch (error) { return ( authzErrorResponse(error) ?? handleError(error, "Failed to delete role", "ROLE_DELETE_ERROR") ); } };